package com.eralink.uhfreaderlib;

import android.util.Log;

import androidx.annotation.NonNull;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

class ProcessCenter {
    private static final String TAG = "ProcessCenter";

    private static final Set<Integer> BAUDS = new HashSet<Integer>() {
        { add(4800); add(9600); add(19200); add(38400); add(57600); add(115200); add(230400); }
    };

    private final Semaphore semaphore = new Semaphore(0);
    private final AtomicInteger waitCmd = new AtomicInteger(0);
    private final Hashtable<Integer, Processor> processors = new Hashtable<>();
    private final Hashtable<Integer, CacheNode> responseCache = new Hashtable<>();
    private final LinkedBlockingDeque<CacheNode> reportQueue = new LinkedBlockingDeque<>();

    private EraLinkUHFReaderReportCallback reportCallback;

    private Reader reader;
    private byte address = 0;
    private volatile boolean threadRunning = false;
    private ReadThread readThread = null;
    private DispatchThread dispatchThread = null;

    private int role = -1; // 0, client; 1, server

    private final Object syncObj = new Object();
    private final HashMap<Byte, Client> clients = new HashMap<>();

    private Selector selector = null;
    private ServerSocketChannel serverSocketChannel = null;
    private MIOThread mioThread = null;

    public ProcessCenter() {
        Processor processor = new Processor(EraLinkUHFReaderCmdCode.CmdSetMacAddress, "set mac address");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdSetMacAddress, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdGetSN, "get sn");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdGetSN));
        processors.put(EraLinkUHFReaderCmdCode.CmdGetSN, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdSetHeartbeatInterval, "set heartbeat/heartbeat report");
        processor.setNeedNotifyRequestor(false);
        processor.setReportUtilizer(this::setHeartbeatReportUtilize);
        processors.put(EraLinkUHFReaderCmdCode.CmdSetHeartbeatInterval, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdGetInventoryStatistics, "get inventory statistics");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdGetInventoryStatistics));
        processors.put(EraLinkUHFReaderCmdCode.CmdGetInventoryStatistics, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdBlockWrite, "block write");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdBlockWrite, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdBlockErase, "block erase");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdBlockErase, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdInventoryOnce, "inventory once");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdInventoryOnce));
        processor.setNeedNotifyRequestor(false);
        processor.setResponseValidator(this::inventoryOnceReportValidator);
        processor.setReportUtilizer(this::inventoryOnceReportUtilize);
        processors.put(EraLinkUHFReaderCmdCode.CmdInventoryOnce, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdReadTag, "read tag");
        processors.put(EraLinkUHFReaderCmdCode.CmdReadTag, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdWriteTag, "write tag");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdWriteTag, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdReaderOpStateReport, "reader operation state report");
        processor.setNeedNotifyRequestor(false);
        processor.setResponseValidator(this::readerOpStateReportValidator);
        processor.setReportUtilizer(this::readerOpStateReportUtilize);
        processors.put(EraLinkUHFReaderCmdCode.CmdReaderOpStateReport, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdGetAntEchoDetectingParams, "get ant echo detecting params");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdGetAntEchoDetectingParams));
        processors.put(EraLinkUHFReaderCmdCode.CmdGetAntEchoDetectingParams, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdStartInventory, "start inventory");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdStartInventory));
        processor.setNeedNotifyRequestor(false);
        processor.setResponseValidator(this::inventoryOnceReportValidator);
        processor.setReportUtilizer(this::inventoryReportUtilize);
        processors.put(EraLinkUHFReaderCmdCode.CmdStartInventory, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdStopInventory, "stop inventory");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdStopInventory));
        processor.setNeedNotifyRequestor(false);
        processor.setResponseValidator(this::stopInventoryReportValidator);
        processor.setReportUtilizer(this::stopInventoryReportUtilize);
        processors.put(EraLinkUHFReaderCmdCode.CmdStopInventory, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdLockTag, "lock tag");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdLockTag, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdKillTag, "kill tag");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdKillTag, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdStartAutoRead, "start auto read");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdStartAutoRead));
        processor.setNeedNotifyRequestor(false);
        processor.setResponseValidator(this::startAutoReadReportValidator);
        processor.setReportUtilizer(this::startAutoReadReportUtilize);
        processors.put(EraLinkUHFReaderCmdCode.CmdStartAutoRead, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdStopAutoRead, "stop auto read");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdStopAutoRead));
        processor.setNeedNotifyRequestor(false);
        processor.setResponseValidator(this::stopAutoReadReportValidator);
        processor.setReportUtilizer(this::stopAutoReadReportUtilize);
        processors.put(EraLinkUHFReaderCmdCode.CmdStopAutoRead, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdUpgrade, "upgrade");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdUpgrade, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdGetVersion, "get version");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdGetVersion));
        processor.setResponseValidator(this::getVersionResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdGetVersion, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdGetAntConfig, "get ant config");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdGetAntConfig));
        processors.put(EraLinkUHFReaderCmdCode.CmdGetAntConfig, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdSetAntConfig, "set ant config");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdSetAntConfig, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdGetGpioTriggerStatus, "get gpio trigger status");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdGetGpioTriggerStatus));
        processors.put(EraLinkUHFReaderCmdCode.CmdGetGpioTriggerStatus, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdSetRelayStatus, "set relay status");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdSetRelayStatus, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdSetQValue, "set q value");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdSetQValue, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdGetQValue, "get q value");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdGetQValue));
        processor.setResponseValidator(this::getQValueResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdGetQValue, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdSetQueryConfig, "set query config");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdSetQueryConfig, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdGetQueryConfig, "get query config");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdGetQueryConfig));
        processor.setResponseValidator(this::getQueryConfigResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdGetQueryConfig, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdSetInventoryConfig, "set inventory config");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdSetInventoryConfig, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdGetInventoryConfig, "get inventory config");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdGetInventoryConfig));
        processor.setResponseValidator(this::getInventoryConfigResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdGetInventoryConfig, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdSetTxOnOffTime, "set tx on off time");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdSetTxOnOffTime, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdGetTxOnOffTime, "get tx on off time");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdGetTxOnOffTime));
        processor.setResponseValidator(this::getTxOnOffTimeResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdGetTxOnOffTime, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdSetAdjustCwConfig, "set adjust cw config");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdSetAdjustCwConfig, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdGetAdjustCwConfig, "get adjust cw config");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdGetAdjustCwConfig));
        processor.setResponseValidator(this::getAdjustCwConfigResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdGetAdjustCwConfig, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdGetReaderState, "get reader state");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdGetReaderState));
        processor.setNeedNotifyRequestor(false);
        processor.setResponseValidator(this::getReaderStateReportValidator);
        processor.setReportUtilizer(this::getReaderStateReportUtilize);
        processors.put(EraLinkUHFReaderCmdCode.CmdGetReaderState, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdReadMacRegister, "read mac register");
        processors.put(EraLinkUHFReaderCmdCode.CmdReadMacRegister, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdWriteMacRegister, "write mac register");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdWriteMacRegister, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdReadOemRegister, "read oem register");
        processors.put(EraLinkUHFReaderCmdCode.CmdReadOemRegister, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdWriteOemRegister, "write oem register");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdWriteOemRegister, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdWriteOemRegisterWithoutReset, "write oem register without reset");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdWriteOemRegisterWithoutReset, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdSetLinkProfile, "set link profile");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdSetLinkProfile, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdGetLinkProfile, "get link profile");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdGetLinkProfile));
        processor.setResponseValidator(this::getLinkProfileResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdGetLinkProfile, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdResetUHF, "reset uhf");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdResetUHF));
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdResetUHF, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdResetReader, "reset reader");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdResetReader));
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdResetReader, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdSwitchCarrierWave, "switch carrier wave");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdSwitchCarrierWave, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdSwitchHopping, "switch hopping");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdSwitchHopping, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdSetTestFrequency, "set test frequency");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdSetTestFrequency, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdSetTestAntPower, "set test ant power");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdSetTestAntPower, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdSelectTag, "select tag");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdSelectTag, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdCancelSelectTag, "cancel select tag");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdCancelSelectTag));
        processor.setResponseUtilizer(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdCancelSelectTag, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdGetSelectTag, "get select tag");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdGetSelectTag));
        processors.put(EraLinkUHFReaderCmdCode.CmdGetSelectTag, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdStartMultiBankAutoRead, "start multi bank auto read");
        processor.setNeedNotifyRequestor(false);
        processor.setResponseValidator(this::startMultiBankAutoReadReportValidator);
        processor.setReportUtilizer(this::startMultiBankAutoReadReportUtilize);
        processors.put(EraLinkUHFReaderCmdCode.CmdStartMultiBankAutoRead, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdStopMultiBankAutoRead, "stop multi bank auto read");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdStopMultiBankAutoRead));
        processor.setNeedNotifyRequestor(false);
        processor.setResponseValidator(this::stopInventoryReportValidator);
        processor.setReportUtilizer(this::stopMultiBankAutoReadReportUtilize);
        processors.put(EraLinkUHFReaderCmdCode.CmdStopMultiBankAutoRead, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdGetDeviceName, "get device name");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdGetDeviceName));
        processor.setResponseValidator(this::getDeviceNameResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdGetDeviceName, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdSetDeviceName, "set device name");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdSetDeviceName, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdDeviceInfoReport, "device information report");
        processor.setNeedNotifyRequestor(false);
        processor.setResponseValidator(this::deviceInfoReportValidator);
        processor.setReportUtilizer(this::deviceInfoReportUtilizer);
        processors.put(EraLinkUHFReaderCmdCode.CmdDeviceInfoReport, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdReaderCommunicationBreak, "reader communication break");
        processor.setNeedNotifyRequestor(false);
        processor.setReportUtilizer(this::readerCommunicationBreakReportUtilizer);
        processors.put(EraLinkUHFReaderCmdCode.CmdReaderCommunicationBreak, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdSetWorkMode, "set work mode");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdSetWorkMode, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdSetCommunicationMode, "set communication mode");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdSetCommunicationMode, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdSetRs485Address, "set rs485 address");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdSetRs485Address, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdSetTriggerModeDelayStopReadTime, "set trigger mode delay stop read time");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdSetTriggerModeDelayStopReadTime, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdSetAutoReadParams, "set auto read params");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdSetAutoReadParams, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdSetRssiFilter, "set rssi filter");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdSetRssiFilter, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdSetTagFilter, "set tag filter");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdSetTagFilter, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdSetWifiParams, "set wifi params");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdSetWifiParams, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdSetGprsParams, "set gprs params");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdSetGprsParams, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdSetBuzzerEnable, "set buzzer enable");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdSetBuzzerEnable, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdSetBaud, "set baud");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdSetBaud, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdSetWgParams, "set wg params");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdSetWgParams, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdSetRtcTime, "set rtc time");
        processor.setResponseValidator(this::noContentResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdSetRtcTime, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdGetWorkMode, "get work mode");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdGetWorkMode));
        processor.setResponseValidator(this::getWorkModeResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdGetWorkMode, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdGetCommunicationMode, "get communication mode");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdGetCommunicationMode));
        processor.setResponseValidator(this::getCommunicationModeResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdGetCommunicationMode, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdGetRs485Address, "get rs485 address");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdGetRs485Address));
        processor.setResponseValidator(this::getRs485AddressResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdGetRs485Address, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdGetTriggerModeDelayStopReadTime, "get trigger mode delay stop read time");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdGetTriggerModeDelayStopReadTime));
        processor.setResponseValidator(this::getTriggerModeDelayStopReadTimeResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdGetTriggerModeDelayStopReadTime, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdGetAutoReadParams, "get auto read params");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdGetAutoReadParams));
        processor.setResponseValidator(this::getAutoReadParamsResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdGetAutoReadParams, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdGetRssiFilter, "get rssi filter");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdGetRssiFilter));
        processor.setResponseValidator(this::getRssiFilterResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdGetRssiFilter, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdGetTagFilter, "get tag filter");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdGetTagFilter));
        processor.setResponseValidator(this::getTagFilterResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdGetTagFilter, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdGetWifiParams, "get wifi params");
        processors.put(EraLinkUHFReaderCmdCode.CmdGetWifiParams, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdGetGprsParams, "get gprs params");
        processors.put(EraLinkUHFReaderCmdCode.CmdGetGprsParams, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdGetBuzzerEnable, "get buzzer enable");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdGetBuzzerEnable));
        processor.setResponseValidator(this::getBuzzerEnableValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdGetBuzzerEnable, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdGetBaud, "get baud");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdGetBaud));
        processor.setResponseValidator(this::getBaudResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdGetBaud, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdGetWgParams, "get wg params");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdGetWgParams));
        processor.setResponseValidator(this::getWgParamsResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdGetWgParams, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdGetRtcTime, "get rtc time");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdGetRtcTime));
        processor.setResponseValidator(this::getRtcTimeResponseValidator);
        processors.put(EraLinkUHFReaderCmdCode.CmdGetRtcTime, processor);

        processor = new Processor(EraLinkUHFReaderCmdCode.CmdGetAllSystemParams, "get all system params");
        processor.setRequestor(reader_id -> noContentRequester(reader_id, EraLinkUHFReaderCmdCode.CmdGetAllSystemParams));
        processors.put(EraLinkUHFReaderCmdCode.CmdGetAllSystemParams, processor);
    }

    public int connect(String connect_str, EraLinkUHFReaderReportCallback reportCallback) {
        if (-1 != role) {
            return EraLinkUHFReaderApiError.ErrAlreadyOpened;
        }

        ConnectConfigParser parser = new ConnectConfigParser();
        if (!parser.parse(connect_str) || !parser.validate()) {
            return EraLinkUHFReaderApiError.ErrBadArg;
        }

        final String[] commType = new String[1];
        parser.getValueByName("CommType", commType);
        if (commType[0].equals("COM")) {
            final String[] comPath = new String[1];
            final String[] baud = new String[1];
            parser.getValueByName("ComPath", comPath);
            parser.getValueByName("Baud", baud);
            reader = new SerialPortReader(comPath[0], Integer.parseInt(baud[0]));
        } else {
            final String[] remoteIp = new String[1];
            final String[] remotePort = new String[1];
            parser.getValueByName("RemoteIp", remoteIp);
            parser.getValueByName("RemotePort", remotePort);
            reader = new NetworkReader(remoteIp[0], Integer.parseInt(remotePort[0]));
        }

        final int result = reader.open();
        if (EraLinkUHFReaderApiError.ErrNoError != result) {
            reader = null;
            return result;
        }

        if (commType[0].equals("COM")) {
            final String[] com_address = new String[1];
            if (parser.getValueByName("Address", com_address)) {
                address = (byte) Integer.parseInt(com_address[0]);
            }
        }

        role = 0;

        this.reportCallback = reportCallback;
        startThread();

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    public int listen(String listen_ip, short listen_port, EraLinkUHFReaderReportCallback reportCallback) {
        if (-1 != role) {
            return EraLinkUHFReaderApiError.ErrAlreadyOpened;
        }

        InetSocketAddress listen_address;
        try {
            listen_address = new InetSocketAddress(listen_ip, listen_port);
        } catch (Exception e) {
            e.printStackTrace();
            return EraLinkUHFReaderApiError.ErrBadArg;
        }

        try {
            selector = Selector.open();
        } catch (IOException e) {
            e.printStackTrace();
            return EraLinkUHFReaderApiError.ErrOpenFailed;
        }

        try {
            serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.socket().bind(listen_address);
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        }  catch (Exception e) {
            e.printStackTrace();
            if (serverSocketChannel != null) {
                try {
                    serverSocketChannel.close();
                } catch (IOException ioException) {
                    ioException.printStackTrace();
                }
            }
            try {
                selector.close();
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }
            selector = null;
            return EraLinkUHFReaderApiError.ErrOpenFailed;
        }

        role = 1;

        this.reportCallback = reportCallback;
        startThread();

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    private void startThread() {
        if (threadRunning) {
            return;
        }

        threadRunning = true;

        if (role == 0) {
            readThread = new ReadThread();
            readThread.start();
        } else {
            mioThread = new MIOThread();
            mioThread.start();
        }

        dispatchThread = new DispatchThread();
        dispatchThread.start();
    }

    private void stopThread() {
        if (!threadRunning) {
            return;
        }

        threadRunning = false;

        if (role == 0) {
            reader.close();

            try {
                readThread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            reader = null;
            readThread = null;
        } else {
            try {
                serverSocketChannel.close();
                selector.close();
                synchronized (syncObj) {
                    for (Map.Entry<Byte, Client> entry : clients.entrySet()) {
                        entry.getValue().close();
                    }
                    clients.clear();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            try {
                mioThread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            serverSocketChannel = null;
            selector = null;
            mioThread = null;
        }

        try {
            dispatchThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        dispatchThread = null;
    }

    public int close() {
        stopThread();
        return 0;
    }

    public int removeReader(byte reader_id) {
        synchronized (syncObj) {
            Client client = clients.get(reader_id);
            if (client != null) {
                client.setMarkedAsRemoved(true);
            }
        }
        return EraLinkUHFReaderApiError.ErrNoError;
    }

    public int setMacAddress(byte reader_id, final byte[] mac_address) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdSetMacAddress);
        processor.setRequestValidator(() ->
                mac_address != null && mac_address.length == 6 && (mac_address[0] & 0xFF) == 0x00
        );
        processor.setRequestor(reader_id_ ->
                writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdSetMacAddress, mac_address, 0, mac_address.length)
        );

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdSetMacAddress);
    }

    public int getSN(byte reader_id, final String[] sn) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdGetSN);
        processor.setRequestValidator(() ->
                sn != null && sn.length == 1
        );
        processor.setResponseValidator(response -> {
            final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
            if (body_len == 1) {
                return (response.getBuffer()[7] & 0xFF) << 8;
            }

            return EraLinkUHFReaderApiError.ErrNoError;
        });
        processor.setResponseUtilizer(response -> {
            final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
            sn[0] = Utils.bytesToHexString(response.getBuffer(), 7, body_len);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdGetSN);
    }

    public int setHeartbeatInterval(byte reader_id, byte interval) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdSetHeartbeatInterval);
        processor.setRequestValidator(() -> 0 < (interval & 0xFF));
        processor.setRequestor(reader_id_ ->
                writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdSetHeartbeatInterval, new byte[]{interval}, 0, 1)
        );

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdSetHeartbeatInterval);
    }

    private void setHeartbeatReportUtilize(@NonNull CacheNode report) {
        final byte read_id = report.getBuffer()[0];
        final int body_len = Utils.u16FromLittleEndianU8s(report.getBuffer(), 5);
        CacheNode cacheNode = CachePool.getInstance().obtain(body_len);
        System.arraycopy(report.getBuffer(), 7, cacheNode.getBuffer(), 0, body_len);
        cacheNode.setBufferSize(body_len);
        reportCallback.report(read_id, EraLinkUHFReaderCmdCode.CmdSetHeartbeatInterval, cacheNode.getBuffer(), cacheNode.getBufferSize());
        CachePool.getInstance().release(cacheNode);
    }

    public int heartbeat(byte reader_id) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdSetHeartbeatInterval);
        processor.setRequestValidator(() -> true);
        processor.setRequestor(reader_id_ -> noContentRequester(reader_id_, EraLinkUHFReaderCmdCode.CmdSetHeartbeatInterval));

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdSetHeartbeatInterval);
    }

    public int getInventoryStatistics(byte reader_id, final byte[] statistics, final int[] statistics_len) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdGetInventoryStatistics);
        processor.setRequestValidator(() ->
                statistics != null && statistics_len != null && statistics_len.length == 1 &&
                        0 < statistics_len[0] && statistics_len[0] <= statistics.length
        );
        processor.setResponseValidator(response -> {
            final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
            if (body_len == 1) {
                return (response.getBuffer()[7] & 0xFF) << 8;
            }

            if (statistics_len[0] < body_len) {
                return EraLinkUHFReaderApiError.ErrInsufficientInputBuffer;
            }

            return EraLinkUHFReaderApiError.ErrNoError;
        });
        processor.setResponseUtilizer(response -> {
            final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
            System.arraycopy(response.getBuffer(), 7, statistics, 0, body_len);
            statistics_len[0] = body_len;
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdGetInventoryStatistics);
    }

    public int blockWrite(byte reader_id, byte bank, short offset, byte length, final byte[] access_password, final byte[] tag_data) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdBlockWrite);
        processor.setRequestValidator(() ->
                // bank: 0x00:Reserved, 0x01:EPC, 0x02:TID, 0x03:USER
                (bank & 0xFF) < 4 && 8 == length && access_password != null && access_password.length == 4 &&
                        tag_data != null && tag_data.length == 16
        );
        processor.setRequestor(reader_id_ -> {
            final byte[] data = new byte[24];
            data[0] = bank;
            Utils.u16ToLittleEndianU8s(offset, data, 1);
            data[3] = length;
            System.arraycopy(access_password, 0, data, 4, 4);
            System.arraycopy(tag_data, 0, data, 8, 16);
            return writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdBlockWrite, data, 0, data.length);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdBlockWrite);
    }

    public int blockErase(byte reader_id, byte bank, short offset, byte length, final byte[] access_password) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdBlockErase);
        processor.setRequestValidator(() ->
                // bank: 0x00:Reserved, 0x01:EPC, 0x02:TID, 0x03:USER
                (bank & 0xFF) < 4 && 0 < (length & 0xFF) && access_password != null && access_password.length == 4
        );
        processor.setRequestor(reader_id_ -> {
            final byte[] data = new byte[8];
            data[0] = bank;
            Utils.u16ToLittleEndianU8s(offset, data, 1);
            data[3] = length;
            System.arraycopy(access_password, 0, data, 4, 4);
            return writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdBlockErase, data, 0, data.length);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdBlockErase);
    }

    public int inventoryOnce(byte reader_id) {
        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdInventoryOnce);
    }

    private int inventoryOnceReportValidator(@NonNull CacheNode report) {
        // PC(2byte)+EPC+CRC(2byte)+ANT(1byte)+RSSI(2byte)[+yymmddhhmmss]
        final int body_len = Utils.u16FromLittleEndianU8s(report.getBuffer(), 5);

        if (body_len == 2 && EraLinkUHFReaderCmdCode.CmdInventoryOnce == (report.getBuffer()[3] & 0xFF)) {
            return EraLinkUHFReaderApiError.ErrNoError;
        }

        if (9 > body_len) {
            return EraLinkUHFReaderApiError.ErrBadReport;
        }

        final int epc_len = ((report.getBuffer()[7] & 0xFF) >> 3) * 2;
        if (body_len < (epc_len + 7)) {
            Log.e(TAG, String.format("epc_data: %s", Utils.bytesToHexString(report.getBuffer(), 0, report.getBufferSize())));
            return EraLinkUHFReaderApiError.ErrBadReport;
        }
        final short crc0 = Utils.u16FromBigEndianU8s(report.getBuffer(), 9 + epc_len);
        final short crc1 = (short) ~Utils.calcEPCCRC(report.getBuffer(), 7, 2 + epc_len);
        if (crc0 != crc1) {
            Log.e(TAG, String.format("crc0: %s, crc1: %s, data: %s", crc0, crc1, Utils.bytesToHexString(report.getBuffer(), 0, report.getBufferSize())));
            return EraLinkUHFReaderApiError.ErrBadReport;
        }

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    private void inventoryOnceReportUtilize(@NonNull CacheNode report) {
        final byte reader_id = report.getBuffer()[0];
        final int body_len = Utils.u16FromLittleEndianU8s(report.getBuffer(), 5);
        CacheNode cacheNode = CachePool.getInstance().obtain(body_len);
        System.arraycopy(report.getBuffer(), 7, cacheNode.getBuffer(), 0, body_len);
        cacheNode.setBufferSize(body_len);
        reportCallback.report(reader_id, EraLinkUHFReaderCmdCode.CmdInventoryOnce, cacheNode.getBuffer(), cacheNode.getBufferSize());
        CachePool.getInstance().release(cacheNode);
    }

    public int readTag(byte reader_id, byte bank, short offset, byte length, final byte[] access_password, final byte[] tag_data) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdReadTag);
        processor.setRequestValidator(() ->
                // bank: 0x00:Reserved, 0x01:EPC, 0x02:TID, 0x03:USER
                (bank & 0xFF) <= 0x03 && 0 < (length & 0xFF) && access_password != null &&
                        access_password.length == 4 && tag_data != null && tag_data.length == 2 * length
        );
        processor.setRequestor(reader_id_ -> {
            final byte[] data = new byte[8];
            data[0] = bank;
            Utils.u16ToLittleEndianU8s(offset, data, 1);
            data[3] = length;
            System.arraycopy(access_password, 0, data, 4, 4);
            return writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdReadTag, data, 0, data.length);
         });
        processor.setResponseValidator(response -> {
            final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
            if (body_len == 1 || body_len == 3) {
                if (body_len == 1) {
                    return (response.getBuffer()[7] & 0xFF) << 8;
                } else {
                    return ((response.getBuffer()[7] & 0xFF) << 8) | ((response.getBuffer()[8] & 0xFF) << 16) |
                            ((response.getBuffer()[9] & 0xFF) << 24);
                }
            }

            if (body_len != 2 * length) {
                return EraLinkUHFReaderApiError.ErrBadResponse;
            }

            return EraLinkUHFReaderApiError.ErrNoError;
        });
        processor.setResponseUtilizer(response -> {
            final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
            System.arraycopy(response.getBuffer(), 7, tag_data, 0, body_len);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdReadTag);
    }

    public int writeTag(byte reader_id, byte bank, short offset, byte length, final byte[] access_password, final byte[] tag_data) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdWriteTag);
        processor.setRequestValidator(() ->
                // bank: 0x00:Reserved, 0x01:EPC, 0x02:TID, 0x03:USER
                (bank & 0xFF) <= 0x03 && 0 < (length & 0xFF) && access_password != null &&
                        access_password.length == 4 && tag_data != null && (2 * length) == tag_data.length
        );
        processor.setRequestor(reader_id_ -> {
            final byte[] data = new byte[8 + tag_data.length];
            data[0] = bank;
            Utils.u16ToLittleEndianU8s(offset, data, 1);
            data[3] = length;
            System.arraycopy(access_password, 0, data, 4, 4);
            System.arraycopy(tag_data, 0, data, 8, tag_data.length);
            return writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdWriteTag, data, 0, data.length);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdWriteTag);
    }

    private int readerOpStateReportValidator(@NonNull CacheNode report){
        final int body_len = Utils.u16FromLittleEndianU8s(report.getBuffer(), 5);
        if (body_len == 1) {
            return (report.getBuffer()[7] & 0xFF) << 8;
        }

        if (body_len != 4) {
            return EraLinkUHFReaderApiError.ErrBadReport;
        }

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    private void readerOpStateReportUtilize(@NonNull CacheNode report) {
        final byte reader_id = report.getBuffer()[0];
        final int body_len = Utils.u16FromLittleEndianU8s(report.getBuffer(), 5);
        CacheNode cacheNode = CachePool.getInstance().obtain(body_len);
        System.arraycopy(report.getBuffer(), 7, cacheNode.getBuffer(), 0, body_len);
        cacheNode.setBufferSize(body_len);
        reportCallback.report(reader_id, EraLinkUHFReaderCmdCode.CmdReaderOpStateReport, cacheNode.getBuffer(), cacheNode.getBufferSize());
        CachePool.getInstance().release(cacheNode);
    }

    public int getAntEchoDetectingParams(byte reader_id, final byte[] params, final int[] params_len) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdGetAntEchoDetectingParams);
        processor.setRequestValidator(() ->
                params != null && params_len != null && params_len.length == 1 && 0 < params_len[0] &&
                        params_len[0] <= params.length
        );
        processor.setResponseValidator(response -> {
            final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
            if (body_len == 1) {
                return (response.getBuffer()[7] & 0xFF) << 8;
            }

            if (params_len[0] < body_len) {
                return EraLinkUHFReaderApiError.ErrInsufficientInputBuffer;
            }

            return EraLinkUHFReaderApiError.ErrNoError;
        });
        processor.setResponseUtilizer(response -> {
            final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
            System.arraycopy(response.getBuffer(), 7, params, 0, body_len);
            params_len[0] = body_len;
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdGetAntEchoDetectingParams);
    }

    public int inventory(byte reader_id) {
        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdStartInventory);
    }

    private void inventoryReportUtilize(@NonNull CacheNode report) {
        final byte reader_id = report.getBuffer()[0];
        final int body_len = Utils.u16FromLittleEndianU8s(report.getBuffer(), 5);
        CacheNode cacheNode = CachePool.getInstance().obtain(body_len);
        System.arraycopy(report.getBuffer(), 7, cacheNode.getBuffer(), 0, body_len);
        cacheNode.setBufferSize(body_len);
        reportCallback.report(reader_id, EraLinkUHFReaderCmdCode.CmdStartInventory, cacheNode.getBuffer(), cacheNode.getBufferSize());
        CachePool.getInstance().release(cacheNode);
    }

    public int stopInventory(byte reader_id) {
        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdStopInventory);
    }

    private int stopInventoryReportValidator(@NonNull CacheNode report) {
        final int body_len = Utils.u16FromLittleEndianU8s(report.getBuffer(), 5);
        if (1 != body_len) {
            return EraLinkUHFReaderApiError.ErrBadReport;
        }

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    private void stopInventoryReportUtilize(@NonNull CacheNode report) {
        final byte reader_id = report.getBuffer()[0];
        final int body_len = Utils.u16FromLittleEndianU8s(report.getBuffer(), 5);
        CacheNode cacheNode = CachePool.getInstance().obtain(body_len);
        System.arraycopy(report.getBuffer(), 7, cacheNode.getBuffer(), 0, body_len);
        cacheNode.setBufferSize(body_len);
        reportCallback.report(reader_id, EraLinkUHFReaderCmdCode.CmdStopInventory, cacheNode.getBuffer(), cacheNode.getBufferSize());
        CachePool.getInstance().release(cacheNode);
    }

    public int lockTag(byte reader_id, byte lock_type, byte bank, final byte[] access_password) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdLockTag);
        processor.setRequestValidator(() ->
            // lockType: 0x00, 开放; 0x01, 锁定; 0x02, 永久开放; 0x03, 永久锁定
            // bank: 0x00, Kill Password; 0x01, Access Password; 0x02, EPC Memory; 0x03, TID Memory; 0x04, User Memory
                (lock_type & 0xFF) <= 3 && (bank & 0xFF) <= 4 && access_password != null && access_password.length == 4
        );
        processor.setRequestor(reader_id_ -> {
            final byte[] data = new byte[6];
            data[0] = lock_type;
            data[1] = bank;
            System.arraycopy(access_password, 0, data, 2, 4);
            return writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdLockTag, data, 0, data.length);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdLockTag);
    }

    public int killTag(byte reader_id, final byte[] kill_password, final byte[] access_password) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdKillTag);
        processor.setRequestValidator(() ->
                kill_password != null && kill_password.length == 4 &&
                        access_password != null && access_password.length == 4
        );
        processor.setRequestor(reader_id_ -> {
            final byte[] data = new byte[8];
            System.arraycopy(kill_password, 0, data, 0, 4);
            System.arraycopy(access_password, 0, data, 4, 4);
            return writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdKillTag, data, 0, data.length);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdKillTag);
    }

    public int startAutoRead(byte reader_id) {
        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdStartAutoRead);
    }

    private int startAutoReadReportValidator(@NonNull CacheNode report) {
        final int body_len = Utils.u16FromLittleEndianU8s(report.getBuffer(), 5);
        if (body_len == 0) {
            return EraLinkUHFReaderApiError.ErrBadReport;
        }

        if (1 == body_len) {
            if (0x00 == report.getBuffer()[7]) {
                return EraLinkUHFReaderApiError.ErrNoError;
            } else {
                return EraLinkUHFReaderApiError.ErrBadReport;
            }
        }

        // 读取区域:0x00 EPC,0x01:TID, 0x02:EPC+TID, 0x03 :USER, 0x04:EPC+USER
        final int bank = report.getBuffer()[7] & 0xFF;
        if (4 < bank) {
            return EraLinkUHFReaderApiError.ErrBadReport;
        }

        if (0x00 == bank) { // EPC_LEN+(PC+EPC+CRC)+ANT_NUM+RSSI
            if (9 > (body_len - 1)) {
                return EraLinkUHFReaderApiError.ErrBadReport;
            }

            final int pc_epc_len = report.getBuffer()[8] & 0xFF;
            if (0 == pc_epc_len || pc_epc_len > (body_len - 5)) {
                return EraLinkUHFReaderApiError.ErrBadReport;
            }

            final short calculated_crc = (short) ~Utils.calcEPCCRC(report.getBuffer(), 9, pc_epc_len - 2);
            final short crc = Utils.u16FromBigEndianU8s(report.getBuffer(), 9 + pc_epc_len - 2);
            if (calculated_crc != crc) {
                return EraLinkUHFReaderApiError.ErrBadReport;
            }
        } else if (0x01 == bank || 0x03 == bank) { // 1: TID_LEN+TID_DATA+ANT_NUM, 3: USER_LEN+USER_DATA+ANT_NUM
            if (4 > (body_len - 1)) {
                return EraLinkUHFReaderApiError.ErrBadReport;
            }

            final int tid_len = report.getBuffer()[8] & 0xFF;
            if (0 == tid_len || tid_len > (body_len - 3)) {
                return EraLinkUHFReaderApiError.ErrBadReport;
            }
        } else /*if (0x02 == bank || 0x04 == bank)*/ { // 2: EPC_LEN+(PC+EPC+CRC)+TID_LEN+TID_DATA+ANT_NUM, 4: PC_LEN+(PC+EPC+CRC)+USER_LEN+USER_DATA+ANT_NUM
            if (9 > (body_len - 1)) {
                return EraLinkUHFReaderApiError.ErrBadReport;
            }

            final int pc_epc_len = report.getBuffer()[8] & 0xFF;
            if (6 > pc_epc_len || pc_epc_len > (body_len - 6)) {
                return EraLinkUHFReaderApiError.ErrBadReport;
            }

            final int tid_len = report.getBuffer()[9 + pc_epc_len] & 0xFF;
            if (2 > tid_len || tid_len > (body_len - pc_epc_len - 4)) {
                return EraLinkUHFReaderApiError.ErrBadReport;
            }

            final short calculated_crc = (short) ~Utils.calcEPCCRC(report.getBuffer(), 9, pc_epc_len - 2);
            final short crc = Utils.u16FromBigEndianU8s(report.getBuffer(), 9 + pc_epc_len - 2);
            if (calculated_crc != crc) {
                return EraLinkUHFReaderApiError.ErrBadReport;
            }
        }

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    private void startAutoReadReportUtilize(@NonNull CacheNode report) {
        final byte reader_id = report.getBuffer()[0];
        final int body_len = Utils.u16FromLittleEndianU8s(report.getBuffer(), 5);
        CacheNode cacheNode = CachePool.getInstance().obtain(body_len);
        System.arraycopy(report.getBuffer(), 7, cacheNode.getBuffer(), 0, body_len);
        cacheNode.setBufferSize(body_len);
        reportCallback.report(reader_id, EraLinkUHFReaderCmdCode.CmdStartAutoRead, cacheNode.getBuffer(), cacheNode.getBufferSize());
        CachePool.getInstance().release(cacheNode);
    }

    public int stopAutoRead(byte reader_id) {
        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdStopAutoRead);
    }

    private int stopAutoReadReportValidator(@NonNull CacheNode report) {
        final int body_len = Utils.u16FromLittleEndianU8s(report.getBuffer(), 5);
        if (body_len != 1) {
            return EraLinkUHFReaderApiError.ErrBadReport;
        }

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    private void stopAutoReadReportUtilize(@NonNull CacheNode report) {
        final byte reader_id = report.getBuffer()[0];
        final int body_len = Utils.u16FromLittleEndianU8s(report.getBuffer(), 5);
        CacheNode cacheNode = CachePool.getInstance().obtain(body_len);
        System.arraycopy(report.getBuffer(), 7, cacheNode.getBuffer(), 0, body_len);
        cacheNode.setBufferSize(body_len);
        reportCallback.report(reader_id, EraLinkUHFReaderCmdCode.CmdStopAutoRead, cacheNode.getBuffer(), cacheNode.getBufferSize());
        CachePool.getInstance().release(cacheNode);
    }

    public int upgrade(byte reader_id, final byte[] data) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdUpgrade);
        processor.setRequestValidator(() -> data != null);
        processor.setRequestor(reader_id_ ->
                writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdUpgrade, data, 0, data.length)
        );

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdUpgrade);
    }

    private int deviceInfoReportValidator(@NonNull CacheNode report) {
        final int  body_len = Utils.u16FromLittleEndianU8s(report.getBuffer(), 5);
        if (1 == body_len) {
            return (report.getBuffer()[7] & 0xFF) << 8;
        }

        final int sn_len = report.getBuffer()[7] & 0xFF;
        if (body_len < 9 || body_len < (sn_len + 3)) {
            return EraLinkUHFReaderApiError.ErrBadResponse;
        }

        // let sn = std::str::from_utf8(&report[8..(8 + sn_len)]);
        // if sn.is_err() {
        //     return UHFReaderErrorCode::ErrBadResponse as c_uint;
        // }

        final int name_len = report.getBuffer()[8 + sn_len] & 0xFF;
        if (name_len != (body_len - sn_len - 2)) {
            return EraLinkUHFReaderApiError.ErrBadResponse;
        }

//        let name = std::str::from_utf8(&report[(8 + sn_len)..(8 + sn_len + name_len)]);
//        if name.is_err() {
//            return UHFReaderErrorCode::ErrBadResponse as c_uint;
//        }

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    private void deviceInfoReportUtilizer(@NonNull CacheNode report) {
        final byte reader_id = report.getBuffer()[0];
//        final int body_len = Utils.u16FromLittleEndianU8s(report.getBuffer(), 5);

        final int sn_len = report.getBuffer()[7] & 0xFF;
        final String mac_address = Utils.bytesToHexString(report.getBuffer(), 8, sn_len);
        final int name_len = report.getBuffer()[8 + sn_len] & 0xFF;
        final String name = new String(report.getBuffer(), 9 + sn_len, name_len);

        String device_info;
        synchronized (syncObj) {
            for (Map.Entry<Byte, Client> item : clients.entrySet()) {
                if (mac_address.equals(item.getValue().getSn()) && reader_id != item.getValue().getReaderId()) {
                    item.getValue().setMarkedAsRemoved(true);
                    break;
                }
            }

            Client client = clients.get(reader_id);
            if (client != null) {
                client.setSn(mac_address);
                device_info = String.format("%s,%s,%s", client.getHostString(), mac_address, name);
            } else {
                device_info = String.format("%s,%s", mac_address, name);
            }
        }

        final byte[] report_data = device_info.getBytes();
        reportCallback.report(reader_id, EraLinkUHFReaderCmdCode.CmdDeviceInfoReport, report_data, report_data.length);
    }

    private void readerCommunicationBreakReportUtilizer(@NonNull CacheNode report) {
        final byte reader_id = report.getBuffer()[0];
        final int body_len = Utils.u16FromLittleEndianU8s(report.getBuffer(), 5);
        CacheNode cacheNode = CachePool.getInstance().obtain(body_len);
        System.arraycopy(report.getBuffer(), 7, cacheNode.getBuffer(), 0, body_len);
        cacheNode.setBufferSize(body_len);
        reportCallback.report(reader_id, EraLinkUHFReaderCmdCode.CmdReaderCommunicationBreak, cacheNode.getBuffer(), cacheNode.getBufferSize());
        CachePool.getInstance().release(cacheNode);
    }

    public int getFirmwareVersion(byte reader_id, final String[] version) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdGetVersion);
        processor.setRequestValidator(() -> version != null && 1 == version.length);
        processor.setResponseUtilizer(response -> {
            final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
            version[0] = new String(response.getBuffer(), 7, body_len);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdGetVersion);
    }

    private int noContentRequester(byte reader_id, int cmd_code) {
        return writeRequest(reader_id, cmd_code, new byte[0], 0, 0);
    }

    private int getVersionResponseValidator(@NonNull CacheNode response) {
        final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
        if (1 == body_len) {
            return (response.getBuffer()[7] & 0xFF) << 8;
        }

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    public int getAntConfig(byte reader_id, final byte[] ant_config, final int[] ant_config_len) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdGetAntConfig);
        processor.setRequestValidator(() ->
                ant_config != null && ant_config_len != null && 1 == ant_config_len.length &&
                        0 < ant_config_len[0] && ant_config_len[0] <= ant_config.length
        );
        processor.setResponseValidator(response -> {
            final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
            if (1 == body_len) {
                return (response.getBuffer()[7] & 0xFF) << 8;
            }

            if (12 > body_len || 0 != body_len % 12) {
                return EraLinkUHFReaderApiError.ErrBadResponse;
            }

            final int ant_num = body_len / 12;
            for (int index = 0; index < ant_num; ++index) {
                final int enable = Utils.u32FromLittleEndianU8s(response.getBuffer(), 7 + 12 * index);
                if (enable != 0 && enable != 1) {
                    return EraLinkUHFReaderApiError.ErrBadResponse;
                }
            }

            if (ant_config_len[0] < body_len) {
                return EraLinkUHFReaderApiError.ErrInsufficientInputBuffer;
            }

            return EraLinkUHFReaderApiError.ErrNoError;
        });
        processor.setResponseUtilizer(response -> {
            final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
            System.arraycopy(response.getBuffer(), 7, ant_config, 0, body_len);
            ant_config_len[0] = body_len;
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdGetAntConfig);
    }

    public int setAntConfig(byte reader_id, final byte[] ant_config) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdSetAntConfig);
        processor.setRequestValidator(() -> {
            if (ant_config == null || 0 == ant_config.length || 0 != (ant_config.length % 12)) {
                return false;
            }

            for (int index = 0; index < (ant_config.length / 12); ++index) {
                final int enable = Utils.u32FromLittleEndianU8s(ant_config, index * 12);
                if (enable != 0 && enable != 1) {
                    return false;
                }
            }

            return true;
        });
        processor.setRequestor(reader_id_ ->
                writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdSetAntConfig, ant_config, 0, ant_config.length)
        );

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdSetAntConfig);
    }

    private int noContentResponseValidator(@NonNull CacheNode response) {
        final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
        if (1 != body_len && 3 != body_len) {
            return EraLinkUHFReaderApiError.ErrBadReport;
        }

        if (1 == body_len) {
            if (0x00 != response.getBuffer()[7]) {
                return (response.getBuffer()[7] & 0xFF) << 8;
            }
        }
        else {
            return ((response.getBuffer()[7] & 0xFF) << 8) | ((response.getBuffer()[8] & 0xFF) << 16)
                    | ((response.getBuffer()[9] & 0xFF) << 24);
        }

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    public int getGpioTriggerStatus(byte reader_id, final byte[] statuses, final int[] statuses_len) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdGetGpioTriggerStatus);
        processor.setRequestValidator(() ->
                statuses != null && statuses_len != null && statuses_len.length == 1 &&
                        0 < statuses_len[0] && statuses_len[0] <= statuses.length
        );
        processor.setResponseValidator(response -> {
            final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
            if (body_len == 1) {
                return (response.getBuffer()[7] & 0xFF) << 8;
            }

            if (statuses_len[0] < body_len) {
                return EraLinkUHFReaderApiError.ErrInsufficientInputBuffer;
            }

            // 0x00:低电平  0x01:高电平
            if (0x01 < (response.getBuffer()[7] & 0xFF)) {
                return EraLinkUHFReaderApiError.ErrBadResponse;
            }

            return EraLinkUHFReaderApiError.ErrNoError;
        });
        processor.setResponseUtilizer(response -> {
            final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
            System.arraycopy(response.getBuffer(), 7, statuses, 0, body_len);
            statuses_len[0] = body_len;
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdGetGpioTriggerStatus);
    }

    public int setRelayStatus(byte reader_id, byte relay_number, byte relay_status) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdSetRelayStatus);
        processor.setRequestValidator(() -> {
            // 0x01, 第一组; 0x02, 第二组
            // 0x01, 导通; 0x00, 断开
            return 1 <= relay_number && relay_number <= 2 && (relay_status & 0xFF) < 2;
        });
        processor.setRequestor(reader_id_ ->
                writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdSetRelayStatus, new byte[]{relay_number, relay_status}, 0, 2)
        );

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdSetRelayStatus);
    }

    public int setQValue(byte reader_id, byte mode, byte start_q_value, byte max_q_value,
                         byte min_q_value, byte threshold_multiplier) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdSetQValue);
        processor.setRequestValidator(() -> (mode & 0xFF) < 0x02); // 0x00, 固定Q值; 0x01, 动态Q值
        processor.setRequestor(reader_id_ -> {
            final byte[] data = new byte[5];
            data[0] = mode;
            data[1] = start_q_value;
            if (0x01 == (mode & 0xFF)) {
                data[2] = max_q_value;
                data[3] = min_q_value;
                data[4] = threshold_multiplier;
            }
            return writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdSetQValue, data, 0, data.length);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdSetQValue);
    }

    public int getQValue(byte reader_id, final byte[] mode, final byte[] start_q_value, final byte[] max_q_value,
                         final byte[] min_q_value, final byte[] threshold_multiplier) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdGetQValue);
        processor.setRequestValidator(() ->
                mode != null && mode.length == 1 && start_q_value != null && start_q_value.length == 1 &&
                        max_q_value != null && max_q_value.length == 1 &&
                        min_q_value != null && min_q_value.length == 1 &&
                        threshold_multiplier != null && threshold_multiplier.length == 1
        );
        processor.setResponseUtilizer(response -> {
            mode[0] = response.getBuffer()[7]; // 0x00, 固定Q值; 0x01, 动态Q值
            start_q_value[0] = response.getBuffer()[8];
            if (0x01 == (mode[0] & 0xFF)) {
                max_q_value[0] = response.getBuffer()[9];
                min_q_value[0] = response.getBuffer()[10];
                threshold_multiplier[0] = response.getBuffer()[11];
            }
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdGetQValue);
    }

    private int getQValueResponseValidator(@NonNull CacheNode response) {
        final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
        if (body_len == 1) {
            return (response.getBuffer()[7] & 0xFF) << 8;
        }

        if (body_len != 5 || 0x01 < (response.getBuffer()[7] & 0xFF)) { // 0x00, 固定Q值; 0x01, 动态Q值
            return EraLinkUHFReaderApiError.ErrBadResponse;
        }

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    public int setQueryConfig(byte reader_id, byte query_target, byte query_session, byte query_select) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdSetQueryConfig);
        processor.setRequestValidator(() ->
                0x01 >= (query_target & 0xFF) && 0x03 >= (query_session & 0xFF) && 0x03 >= (query_select & 0xFF)
        );
        processor.setRequestor(reader_id_ -> {
            final int value = ((query_target & 0xFF) << 4) | ((query_session & 0xFF) << 5) | ((query_select & 0xFF) << 7);
            final byte[] data = new byte[4];
            Utils.u32ToLittleEndianU8s(value, data, 0);
            return writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdSetQueryConfig, data, 0, data.length);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdSetQueryConfig);
    }

    public int getQueryConfig(byte reader_id, final byte[] query_target, final byte[] query_session, final byte[] query_select) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdGetQueryConfig);
        processor.setRequestValidator(() ->
                query_target != null && query_target.length == 1 &&
                        query_session != null && query_session.length == 1 &&
                        query_select != null && query_select.length == 1
        );
        processor.setResponseUtilizer(response -> {
            final int value = Utils.u32FromLittleEndianU8s(response.getBuffer(), 7);
            query_target[0] = (byte) ((value >> 4) & 0x01);
            query_session[0] = (byte) ((value >> 5) & 0x03);
            query_select[0] = (byte) ((value >> 7) & 0x03);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdGetQueryConfig);
    }

    private int getQueryConfigResponseValidator(@NonNull CacheNode response) {
        final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
        if (body_len == 1) {
            return (response.getBuffer()[7] & 0xFF) << 8;
        }

        if (body_len != 4) {
            return EraLinkUHFReaderApiError.ErrBadResponse;
        }

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    public int setInventoryConfig(byte reader_id, byte inventory_algorithm, byte match_report,
                                  byte auto_select, byte disable_inventory) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdSetInventoryConfig);
        processor.setRequestValidator(() ->
                0x3F >= (inventory_algorithm & 0xFF) && 0x01 >= (auto_select & 0xFF) && 0x01 >= (disable_inventory & 0xFF)
        );
        processor.setRequestor(reader_id_ -> {
            final int value = (inventory_algorithm & 0xFF) | ((match_report & 0xFF) << 6) | ((auto_select & 0xFF) << 14) |
                    ((disable_inventory & 0xFF) << 15);
            final byte[] data = new byte[4];
            Utils.u32ToLittleEndianU8s(value, data, 0);
            return writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdSetInventoryConfig, data, 0, data.length);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdSetInventoryConfig);
    }

    public int getInventoryConfig(byte reader_id, final byte[] inventory_algorithm, final byte[] match_report,
                                  final byte[] auto_select, final byte[] disable_inventory) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdGetInventoryConfig);
        processor.setRequestValidator(() ->
                inventory_algorithm != null && inventory_algorithm.length == 1 &&
                        match_report != null && match_report.length == 1 &&
                        auto_select != null && auto_select.length == 1 &&
                        disable_inventory != null && disable_inventory.length == 1
        );
        processor.setResponseUtilizer(response -> {
            final int value = Utils.u32FromLittleEndianU8s(response.getBuffer(), 7);
            inventory_algorithm[0] = (byte) (value & 0x3F);
            match_report[0] = (byte) ((value >> 6) & 0xFF);
            auto_select[0] = (byte) ((value >> 14) & 0x01);
            disable_inventory[0] = (byte) ((value >> 15) & 0x01);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdGetInventoryConfig);
    }

    private int getInventoryConfigResponseValidator(@NonNull CacheNode response) {
        final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
        if (body_len == 1) {
            return (response.getBuffer()[7] & 0xFF) << 8;
        }

        if (body_len != 4) {
            return EraLinkUHFReaderApiError.ErrBadResponse;
        }

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    public int setTxOnOffTime(byte reader_id, byte mode, short tx_on_time, short tx_off_time) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdSetTxOnOffTime);
        processor.setRequestValidator(() -> {
            // 0x01:TX on开启,TX off 关闭; 0x11:TX on开启,TX off 开启
            return 0x01 == mode || 0x11 == mode;
        });
        processor.setRequestor(reader_id_ -> {
            final byte[] data = new byte[5];
            data[0] = mode;
            Utils.u16ToLittleEndianU8s(tx_on_time, data, 1);
            Utils.u16ToLittleEndianU8s(tx_off_time, data, 3);
            return writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdSetTxOnOffTime, data, 0, data.length);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdSetTxOnOffTime);
    }

    public int getTxOnOffTime(byte reader_id, final byte[]  mode, final short[] tx_on_time, final short[] tx_off_time) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdGetTxOnOffTime);
        processor.setRequestValidator(() ->
                mode != null && mode.length == 1 && tx_on_time != null && tx_on_time.length == 1 &&
                        tx_off_time != null && tx_off_time.length == 1
        );
        processor.setResponseUtilizer(response -> {
            mode[0] = response.getBuffer()[7];
            tx_on_time[0] = Utils.u16FromLittleEndianU8s(response.getBuffer(), 8);
            tx_off_time[0] = Utils.u16FromLittleEndianU8s(response.getBuffer(), 10);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdGetTxOnOffTime);
    }

    private int getTxOnOffTimeResponseValidator(@NonNull CacheNode response) {
        final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
        if (body_len == 1) {
            return (response.getBuffer()[7] & 0xFF) << 8;
        }

        if (body_len != 5) {
            return EraLinkUHFReaderApiError.ErrBadResponse;
        }

        // 0x01:TX on开启,TX off 关闭; 0x11:TX on开启,TX off 开启
        if (0x01 != (response.getBuffer()[7] & 0xFF) && 0x11 != (response.getBuffer()[7] & 0xFF)) {
            return EraLinkUHFReaderApiError.ErrBadResponse;
        }

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    public int setAdjustCwConfig(byte reader_id, byte adjust_time) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdSetAdjustCwConfig);
        processor.setRequestor(reader_id_ -> {
            final byte[] data = new byte[4];
            Utils.u32ToLittleEndianU8s(adjust_time & 0xFF, data, 0);
            return writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdSetAdjustCwConfig, data, 0, data.length);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdSetAdjustCwConfig);
    }

    public int getAdjustCwConfig(byte reader_id, final byte[] adjust_time) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdGetAdjustCwConfig);
        processor.setRequestValidator(() -> adjust_time != null && adjust_time.length == 1);
        processor.setResponseUtilizer(response ->
                adjust_time[0] = (byte) (Utils.u32FromLittleEndianU8s(response.getBuffer(), 7) & 0xFF)
        );

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdGetAdjustCwConfig);
    }

    private int getAdjustCwConfigResponseValidator(@NonNull CacheNode response) {
        final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
        if (body_len == 1) {
            return (response.getBuffer()[7] & 0xFF) << 8;
        }

        if (body_len != 4) {
            return EraLinkUHFReaderApiError.ErrBadResponse;
        }

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    public int getReaderState(byte reader_id) {
        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdGetReaderState);
    }

    private int getReaderStateReportValidator(@NonNull CacheNode report) {
        final int body_len = Utils.u16FromLittleEndianU8s(report.getBuffer(), 5);
        if (body_len == 1) {
            return (report.getBuffer()[7] & 0xFF) << 8;
        }

        if (body_len != 8) {
            return EraLinkUHFReaderApiError.ErrBadReport;
        }

        final int mac_address = Utils.u32FromLittleEndianU8s(report.getBuffer(), 7);
        if (0x00000005 != mac_address && 0x00000006 != mac_address) {
            return EraLinkUHFReaderApiError.ErrBadReport;
        }

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    private void getReaderStateReportUtilize(@NonNull CacheNode report) {
        final byte reade_id = report.getBuffer()[0];
        final int body_len = Utils.u16FromLittleEndianU8s(report.getBuffer(), 5);
        CacheNode cacheNode = CachePool.getInstance().obtain(body_len);
        System.arraycopy(report.getBuffer(), 7, cacheNode.getBuffer(), 0, body_len);
        cacheNode.setBufferSize(body_len);
        reportCallback.report(reade_id, EraLinkUHFReaderCmdCode.CmdGetReaderState, cacheNode.getBuffer(), cacheNode.getBufferSize());
        CachePool.getInstance().release(cacheNode);
    }

    public int readMacRegister(byte reader_id, int address, final int[] value) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdReadMacRegister);
        processor.setRequestValidator(() -> 0 <= address && address <= 0xFFFF && value != null && value.length == 1);
        processor.setRequestor(reader_id_ -> {
            final byte[] data = new byte[4];
            Utils.u32ToLittleEndianU8s(address, data, 0);
            return writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdReadMacRegister, data, 0, data.length);
        });
        processor.setResponseValidator(response -> {
            final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
            if (1 == body_len) {
                return (response.getBuffer()[7] & 0xFF) << 8;
            }

            if (8 != body_len) {
                return EraLinkUHFReaderApiError.ErrBadResponse;
            }

            final int returned_address = Utils.u32FromLittleEndianU8s(response.getBuffer(), 7);
            if (returned_address != address) {
                return EraLinkUHFReaderApiError.ErrBadResponse;
            }

            return EraLinkUHFReaderApiError.ErrNoError;
        });
        processor.setResponseUtilizer(response ->
                value[0] = Utils.u32FromLittleEndianU8s(response.getBuffer(), 11)
        );

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdReadMacRegister);
    }

    public int writeMacRegister(byte reader_id, int address, int value) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdWriteMacRegister);
        processor.setRequestValidator(() -> 0 <= address && address <= 0xFFFF);
        processor.setRequestor(reader_id_ -> {
            final byte[] data = new byte[8];
            Utils.u32ToLittleEndianU8s(address, data, 0);
            Utils.u32ToLittleEndianU8s(value, data, 4);
            return writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdWriteMacRegister, data, 0, data.length);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdWriteMacRegister);
    }

    public int readOemRegister(byte reader_id, int address, final int[] value) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdReadOemRegister);
        processor.setRequestValidator(() -> value != null && value.length == 1);
        processor.setRequestor(reader_id_ -> {
            final byte[] data = new byte[4];
            Utils.u32ToLittleEndianU8s(address, data, 0);
            return writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdReadOemRegister, data, 0, data.length);
        });
        processor.setResponseValidator(response -> {
            final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
            if (1 == body_len) {
                return (response.getBuffer()[7] & 0xFF) << 8;
            }

            if (8 != body_len) {
                return EraLinkUHFReaderApiError.ErrBadResponse;
            }

            final int returned_address = Utils.u32FromLittleEndianU8s(response.getBuffer(), 7);
            if (returned_address != address) {
                return EraLinkUHFReaderApiError.ErrBadResponse;
            }

            return EraLinkUHFReaderApiError.ErrNoError;
        });
        processor.setResponseUtilizer(response -> value[0] = Utils.u32FromLittleEndianU8s(response.getBuffer(), 11));

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdReadOemRegister);
    }

    public int writeOemRegister(byte reader_id, int address, int value) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdWriteOemRegister);
        processor.setRequestor(reader_id_ -> {
            final byte[] data = new byte[8];
            Utils.u32ToLittleEndianU8s(address, data, 0);
            Utils.u32ToLittleEndianU8s(value, data, 4);
            return writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdWriteOemRegister, data, 0, data.length);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdWriteOemRegister);
    }

    public int writeOemRegisterWithoutReset(byte reader_id, int address, int value) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdWriteOemRegisterWithoutReset);
        processor.setRequestor(reader_id_ -> {
            final byte[] data = new byte[8];
            Utils.u32ToLittleEndianU8s(address, data, 0);
            Utils.u32ToLittleEndianU8s(value, data, 4);
            return writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdWriteOemRegisterWithoutReset, data, 0, data.length);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdWriteOemRegisterWithoutReset);
    }

    public int setLinkProfile(byte reader_id, byte link_profile) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdSetLinkProfile);
        processor.setRequestValidator(() -> {
            // 0x00, DSB_ASK/M0/40kHz; 0x01, PR_ASK/M2/250kHz; 0x02, PR _ASK/M2/300kHz; 0x03, DSB_ASK/M0/400kHz; 0x04, DSB_ASK/M0/640kHz
            return (link_profile & 0xFF) <= 4;
        });
        processor.setRequestor(reader_id_ ->
                writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdSetLinkProfile, new byte[]{link_profile}, 0, 1)
        );

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdSetLinkProfile);
    }

    public int getLinkProfile(byte reader_id, final byte[] link_profile) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdGetLinkProfile);
        processor.setRequestValidator(() -> link_profile != null && link_profile.length == 1);
        processor.setResponseUtilizer(response -> link_profile[0] = response.getBuffer()[7]);

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdGetLinkProfile);
    }

    private int getLinkProfileResponseValidator(@NonNull CacheNode response) {
        final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
        if (body_len != 1) {
            return EraLinkUHFReaderApiError.ErrBadResponse;
        }

        // 0x00, DSB_ASK/M0/40kHz; 0x01, PR_ASK/M2/250kHz; 0x02, PR _ASK/M2/300kHz; 0x03, DSB_ASK/M0/400kHz; 0x04, DSB_ASK/M0/640kHz
        if (4 < (response.getBuffer()[7] & 0xFF)) {
            return (response.getBuffer()[7] & 0xFF) << 8;
        }

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    public int resetUHF(byte reader_id) {
        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdResetUHF);
    }

    public int resetReader(byte reader_id) {
        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdResetReader);
    }

    public int switchCarrierWave(byte reader_id, byte toggle) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdSwitchCarrierWave);
        processor.setRequestValidator(() -> (toggle & 0xFF) < 2); // 0, off; 1, on
        processor.setRequestor(reader_id_ ->
                writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdSwitchCarrierWave, new byte[]{toggle}, 0, 1)
        );

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdSwitchCarrierWave);
    }

    public int switchHopping(byte reader_id, byte toggle, byte channel_index) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdSwitchHopping);
        processor.setRequestValidator(() -> (toggle & 0xFF) < 2); // 0, off; 1, on
        processor.setRequestor(reader_id_ ->
                writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdSwitchHopping, new byte[]{toggle, channel_index}, 0, 2)
        );

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdSwitchHopping);
    }

    public int setTestFrequency(byte reader_id, int frequency) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdSetTestFrequency);
        processor.setRequestor(reader_id_ -> {
            final byte[] data = new byte[4];
            Utils.u32ToLittleEndianU8s(frequency, data, 0);
            return writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdSetTestFrequency, data, 0, data.length);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdSetTestFrequency);
    }

    public int setTestAntPower(byte reader_id, byte ant, int power) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdSetTestAntPower);
        processor.setRequestor(reader_id_ -> {
            final byte[] data = new byte[5];
            data[0] = ant;
            Utils.u32ToLittleEndianU8s(power, data, 1);
            return writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdSetTestAntPower, data, 0, data.length);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdSetTestAntPower);
    }

    public int selectTag(byte reader_id, byte bank, short offset, short length, final byte[] tag_data) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdSelectTag);
        processor.setRequestValidator(() -> {
            // bank - 0x00:Reserved, 0x01:EPC, 0x02:TID, 0x03:USER
            return (bank & 0xFF) <= 3 && 0 < (length & 0xFFFF) && tag_data != null &&
                    ((length & 0xFFFF) * 2) == tag_data.length;
        });
        processor.setRequestor(reader_id_ -> {
            final byte[] data = new byte[5 + tag_data.length];
            data[0] = bank;
            Utils.u16ToLittleEndianU8s((short) (offset << 4), data, 1);
            Utils.u16ToLittleEndianU8s((short) (length << 4), data, 3);
            System.arraycopy(tag_data, 0, data, 5, tag_data.length);
            return writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdSelectTag, data, 0, data.length);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdSelectTag);
    }

    public int cancelSelectTag(byte reader_id) {
        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdCancelSelectTag);
    }

    public int getSelectTag(byte reader_id, final byte[] select_tag, final int[] select_tag_len) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdGetSelectTag);
        processor.setRequestValidator(() ->
                select_tag != null && select_tag_len != null && 0 < select_tag_len[0] &&
                        select_tag_len[0] <= select_tag.length
        );
        processor.setResponseValidator(response -> {
            final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
            if (body_len == 1) {
                return (response.getBuffer()[7] & 0xFF) << 8;
            }

            if (select_tag_len[0] < body_len) {
                return EraLinkUHFReaderApiError.ErrInsufficientInputBuffer;
            }

            return EraLinkUHFReaderApiError.ErrNoError;
        });
        processor.setResponseUtilizer(response -> {
            final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
            System.arraycopy(response.getBuffer(), 7, select_tag, 0, body_len);
            select_tag_len[0] = body_len;
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdGetSelectTag);
    }

    public int startMultiBankAutoRead(byte reader_id, final byte[] params) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdStartMultiBankAutoRead);
        processor.setRequestValidator(() -> {
            // Data[0]	选择Bank	Bit 0: Reserve  Bit 1:not use  Bit 2:TID Bit 3: USER，将需要的读取的bank区置1
            // Data[2…1]	Offset	Read Reserve Offset
            // Data[3]	CNT	Read Reserve CNT
            // Data[5…4]	Offset	Not use
            // Data[6]	CNT	Not user
            // Data[8…7]	Offset	Read TID Offset
            // Data[9]	CNT	Read TID CNT
            // Data[11…10]	Offset	Read USER Offset
            // Data[12]	CNT	Read USER CNT
            // Data[16]….Data[13]	Password	Access password
            if (params == null || 17 != params.length) {
                return false;
            }

            if (0 == (params[0] & 0b1101)) {
                return false;
            }

            if (0 != (params[0] & 0b0001) && 0 == params[3]) {
                return false;
            }

            if (0 != (params[0] & 0b0100) && 0 == params[9]) {
                return false;
            }

            if (0 != (params[0] & 0b1000) && 0 == params[12]) {
                return false;
            }

            return true;
        });
        processor.setRequestor(reader_id_ ->
                writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdStartMultiBankAutoRead, params, 0, params.length)
        );

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdStartMultiBankAutoRead);
    }

    private int startMultiBankAutoReadReportValidator(@NonNull CacheNode report) {
        final int body_len = Utils.u16FromLittleEndianU8s(report.getBuffer(), 5);
        if (body_len == 1) {
            return (report.getBuffer()[7] & 0xFF) << 8;
        }

        if (12 > report.getBufferSize()) {
            return EraLinkUHFReaderApiError.ErrBadReport;
        }

        final int epc_len = report.getBuffer()[7] & 0xFF;
        if (6 > epc_len || epc_len > (body_len - 5)) {
            return EraLinkUHFReaderApiError.ErrBadReport;
        }

        final short calculated_crc = (short) ~Utils.calcEPCCRC(report.getBuffer(), 8, epc_len - 2);
        final short crc = Utils.u16FromBigEndianU8s(report.getBuffer(), 8 + epc_len - 2);
        if (calculated_crc != crc) {
            return EraLinkUHFReaderApiError.ErrBadReport;
        }

        // Bank 0：RESERVE 2:TID 3：USER
        final int bank = report.getBuffer()[8 + epc_len] & 0xFF;
        if (1 == bank || 3 < bank) {
            return EraLinkUHFReaderApiError.ErrBadReport;
        }

        final int bank_data_len = report.getBuffer()[8 + epc_len + 1] & 0xFF;
        if (0 != (bank_data_len % 2) || (body_len - epc_len - 4) != bank_data_len) {
            return EraLinkUHFReaderApiError.ErrBadReport;
        }

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    private void startMultiBankAutoReadReportUtilize(@NonNull CacheNode report) {
        final byte reader_id = report.getBuffer()[0];
        final int body_len = Utils.u16FromLittleEndianU8s(report.getBuffer(), 5);
        CacheNode cacheNode = CachePool.getInstance().obtain(body_len);
        System.arraycopy(report.getBuffer(), 7, cacheNode.getBuffer(), 0, body_len);
        cacheNode.setBufferSize(body_len);
        reportCallback.report(reader_id, EraLinkUHFReaderCmdCode.CmdStartMultiBankAutoRead, cacheNode.getBuffer(), cacheNode.getBufferSize());
        CachePool.getInstance().release(cacheNode);
    }

    public int stopMultiBankAutoRead(byte reader_id) {
        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdStopMultiBankAutoRead);
    }

    private void stopMultiBankAutoReadReportUtilize(@NonNull CacheNode report) {
        final byte reader_id = report.getBuffer()[0];
        final int body_len = Utils.u16FromLittleEndianU8s(report.getBuffer(), 5);
        CacheNode cacheNode = CachePool.getInstance().obtain(body_len);
        System.arraycopy(report.getBuffer(), 7, cacheNode.getBuffer(), 0, body_len);
        cacheNode.setBufferSize(body_len);
        reportCallback.report(reader_id, EraLinkUHFReaderCmdCode.CmdStopMultiBankAutoRead, cacheNode.getBuffer(), cacheNode.getBufferSize());
        CachePool.getInstance().release(cacheNode);
    }

    public int getDeviceName(byte reader_id, final String[] device_name) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdGetDeviceName);
        processor.setRequestValidator(() -> device_name != null && device_name.length == 1);
        processor.setResponseUtilizer(response -> {
            final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
            device_name[0] = new String(response.getBuffer(), 7, body_len);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdGetDeviceName);
    }

    private int getDeviceNameResponseValidator(@NonNull CacheNode response) {
        final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
        if (body_len == 1) {
            return (response.getBuffer()[7] & 0xFF) << 8;
        }

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    public int setDeviceName(byte reader_id, String device_name) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdSetDeviceName);
        final byte[][] data = new byte[1][1];
        processor.setRequestValidator(() -> {
            if (device_name == null || device_name.isEmpty() || 20 < device_name.length()) {
                return false;
            }

            data[0] = device_name.getBytes();
            return device_name.length() == data[0].length;
        });
        processor.setRequestor(reader_id_ ->
                writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdSetDeviceName, data[0], 0, data[0].length)
        );

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdSetDeviceName);
    }

    public int setWorkMode(byte reader_id, byte work_mode) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdSetWorkMode);
        processor.setRequestValidator(() -> {
            // 0x00:命令模式 0x01: 自动模式 0x02:触发模式
            return (work_mode & 0xFF) <= 2;
        });
        processor.setRequestor(reader_id_ ->
                writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdSetWorkMode, new byte[]{work_mode}, 0, 1)
        );

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdSetWorkMode);
    }

    public int setCommunicationMode(byte reader_id, byte communication_mode) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdSetCommunicationMode);
        processor.setRequestValidator(() -> {
            // 0x00:RJ45, 0x01: RS232, 0x02:RS485, 0x03:WG, 0x04:WIFI, 0x05:GPRS, 0x06:USB
            return (communication_mode & 0xFF) <= 6;
        });
        processor.setRequestor(reader_id_ ->
                writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdSetCommunicationMode, new byte[]{communication_mode}, 0, 1)
        );

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdSetCommunicationMode);
    }

    public int setRs485Address(byte reader_id, byte rs485_address) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdSetRs485Address);
        processor.setRequestValidator(() -> (rs485_address & 0xFF) < 0xFF);// 设备地址，取值范围0~0xFE
        processor.setRequestor(reader_id_ ->
                writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdSetRs485Address, new byte[]{rs485_address}, 0, 1)
        );

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdSetRs485Address);
    }

    public int setTriggerModeDelayStopReadTime(byte reader_id, byte time) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdSetTriggerModeDelayStopReadTime);
        processor.setRequestor(reader_id_ ->
                writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdSetTriggerModeDelayStopReadTime, new byte[]{time}, 0, 1)
        );

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdSetTriggerModeDelayStopReadTime);
    }

    public int setAutoReadParams(byte reader_id, byte bank, short offset, byte length, final byte[] access_password) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdSetAutoReadParams);
        processor.setRequestValidator(() -> {
            // 读取区域:0x00 EPC,0x01:TID, 0x02:EPC+TID, 0x03 :USER, 0x04:EPC+USER
            return (bank & 0xFF) <= 4 && 0 < (length & 0xFF) && access_password != null &&
                    access_password.length == 4;
        });
        processor.setRequestor(reader_id_ -> {
            final byte[] data = new byte[8];
            data[0] = bank;
            Utils.u16ToLittleEndianU8s(offset, data, 1);
            data[3] = length;
            System.arraycopy(access_password, 0, data, 4, 4);
            return writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdSetAutoReadParams, data, 0, data.length);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdSetAutoReadParams);
    }

    public int setRssiFilter(byte reader_id, byte enable, short rssi_value) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdSetRssiFilter);
        processor.setRequestValidator(() -> {
            // 0:Disable Filter  1:Enable Filter
            return (enable & 0xFF) <= 1;
        });
        processor.setRequestor(reader_id_ -> {
            final byte[] data = new byte[3];
            data[0] = enable;
            Utils.u16ToLittleEndianU8s((short) (0xFFFF - (rssi_value & 0xFFFF)), data, 1);
            return writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdSetRssiFilter, data, 0, data.length);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdSetRssiFilter);
    }

    public int setTagFilter(byte reader_id, byte enable, byte filter_time) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdSetTagFilter);
        processor.setRequestValidator(() -> {
            // 0:Disable Filter  1:Enable Filter
            return (enable & 0xFF) <= 1;
        });
        processor.setRequestor(reader_id_ -> {
            final byte[] data = new byte[]{enable, filter_time};
            return writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdSetTagFilter, data, 0, data.length);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdSetTagFilter);
    }

    public int setWifiParams(byte reader_id, byte index, String params) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdSetWifiParams);
        final byte[][] content = new byte[1][1];
        processor.setRequestValidator(() -> {
            // 0: ip,  1： ap, 2： sock,  3： mac(not supported for set)
            if (2 < (index & 0xFF) || params == null|| 3 > params.length()) {
                return false;
            }

            content[0] = params.getBytes();
            if (params.length() != content[0].length) {
                return false;
            }

            final String[] fields = params.split(",");
            if (0 == index) { // ip: ipmode, ip, mask, gateway, dns
                if (4 > params.length()) {
                    return false;
                }

                if (1 == fields.length) {
                    final String ip_mode = fields[0].toUpperCase();
                    return ip_mode.equals("DHCP");
                }
                else if (5 != fields.length) {
                    return false;
                }

                final String ip_mode = fields[0].toUpperCase();
                if (!ip_mode.equals("STATIC")) {
                    return false;
                }
                if (!Utils.ipCheck(fields[1]) || !Utils.ipCheck(fields[2]) || !Utils.ipCheck(fields[3]) ||
                        !Utils.ipCheck(fields[4])) {
                    return false;
                }
            }
            else if (1 == index) { // ap: ssid, key
                if (2 != fields.length) {
                    return false;
                }

                final String ssid = fields[0];
                final String key = fields[1];
                if (ssid.isEmpty() || 32 < ssid.length() || key.isEmpty() || 32 < key.length()) {
                    return false;
                }
            }
            else if (2 == index) { // socket: mode, ip, port
                if (14 > params.length() || 3 != fields.length) {
                    return false;
                }

                final String mode = fields[0].toUpperCase();
                if ((!mode.equals("TCPC") && !mode.equals("TCPS")) || !Utils.ipCheck(fields[1]) ||
                        !Utils.isValidPort(fields[2])) {
                    return false;
                }
            }

            return true;
        });
        processor.setRequestor(reader_id_ -> {
            final byte[] data = new byte[1 + content[0].length];
            data[0] = index;
            System.arraycopy(content[0], 0, data, 1, content[0].length);
            return writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdSetWifiParams, data, 0, data.length);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdSetWifiParams);
    }

    public int setGprsParams(byte reader_id, byte index, String params) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdSetGprsParams);
        final byte[][] content = new byte[1][1];
        processor.setRequestValidator(() -> {
            // 0: socket,  1： apn
            if (1 < (index & 0xFF) || params == null || 3 > params.length()) {
                return false;
            }

            content[0] = params.getBytes();
            if (params.length() != content[0].length) {
                return false;
            }

            final String[] fields = params.split(",");
            if (0 == index) { // socket: protocol, ip/domain, port
                if (10 > params.length() || 3 != fields.length) {
                    return false;
                }

                final String protocol = fields[0].toUpperCase();
                // let ip = fields[1];
                if (!protocol.equals("UDP") && !protocol.equals("TCP")) {
                    return false;
                }
                return Utils.isValidPort(fields[2]);
            } else { // apn: apn, account, password, auth
                if (4 > params.length() || 4 != fields.length) {
                    return false;
                }

                final String apn = fields[0];
                final String account = fields[1];
                final String password = fields[2];
                final Integer auth = Utils.tryParseInt(fields[3]);// 0, none; 1, pap; 2, chap; 3, pap+chap
                if (32 < apn.length() || 32 < account.length() || 32 < password.length() || auth == null ||
                        0 > auth || 3 < auth) {
                    return false;
                }
            }

            return true;
        });
        processor.setRequestor(reader_id_ -> {
            final byte[] data = new byte[1 + content[0].length];
            data[0] = index;
            System.arraycopy(content[0], 0, data, 1, content[0].length);
            return writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdSetGprsParams, data, 0, data.length);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdSetGprsParams);
    }

    public int setBuzzerEnable(byte reader_id, byte enable) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdSetBuzzerEnable);
        processor.setRequestValidator(() -> (enable & 0xFF) < 2);// 0：关闭   1：开启
        processor.setRequestor(reader_id_ ->
                writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdSetBuzzerEnable, new byte[]{enable}, 0, 1)
        );

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdSetBuzzerEnable);
    }

    public int setBaud(byte reader_id, int baud) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdSetBaud);
        processor.setRequestValidator(() -> BAUDS.contains(baud));
        processor.setRequestor(reader_id_ -> {
            final byte[] data = new byte[4];
            Utils.u32ToLittleEndianU8s(baud, data, 0);
            return writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdSetBaud, data, 0, data.length);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdSetBaud);
    }

    public int setWgParams(byte reader_id, byte protocol_type, byte position, byte pulse_width, byte pulse_cycle,
                           byte data_interval) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdSetWgParams);
        processor.setRequestValidator(() -> {
            // 韦根数据输出格式: 0, 韦根26; 1, 韦根34.
            // 韦根数据地址: 0~9
            // 韦根脉冲宽度: 1~255 * 10us
            // 韦根脉冲周期: 1~255 * 10us
            // 韦根数据间隔: 1~255 * 100ms
            return 1 >= (protocol_type & 0xFF) && 9 >= (position & 0xFF) && 0 != (pulse_width & 0xFF) &&
                    0 != (pulse_cycle & 0xFF) && 0 != (data_interval & 0xFF);
        });
        processor.setRequestor(reader_id_ -> {
            final byte[] data = new byte[]{protocol_type, position, pulse_width, pulse_cycle, data_interval};
            return writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdSetWgParams, data, 0, data.length);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdSetWgParams);
    }

    public int setRtcTime(byte reader_id, final byte[] rtc_time) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdSetRtcTime);
        processor.setRequestValidator(() -> rtc_time != null && rtc_time.length == 7);
        processor.setRequestor(reader_id_ ->
                writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdSetRtcTime, rtc_time, 0, rtc_time.length)
        );

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdSetRtcTime);
    }

    public int getWorkMode(byte reader_id, final byte[] work_mode) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdGetWorkMode);
        processor.setRequestValidator(() -> work_mode != null && work_mode.length == 1);
        processor.setResponseUtilizer(response -> work_mode[0] = response.getBuffer()[7]);

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdGetWorkMode);
    }

    private int getWorkModeResponseValidator(@NonNull CacheNode response) {
        final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
        if (1 != body_len) {
            return EraLinkUHFReaderApiError.ErrBadResponse;
        }

        // 0x00:命令模式 0x01: 自动模式 0x02:触发模式
        if (0x02 < (response.getBuffer()[7] & 0xFF)) {
            return (response.getBuffer()[7] & 0xFF) << 8;
        }

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    public int getCommunicationMode(byte reader_id, final byte[] communication_mode) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdGetCommunicationMode);
        processor.setRequestValidator(() -> communication_mode != null && communication_mode.length == 1);
        processor.setResponseUtilizer(response -> communication_mode[0] = response.getBuffer()[7]);

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdGetCommunicationMode);
    }

    private int getCommunicationModeResponseValidator(@NonNull CacheNode response) {
        final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
        if (1 != body_len) {
            return EraLinkUHFReaderApiError.ErrBadResponse;
        }

        // 0x00:RJ45, 0x01: RS232, 0x02:RS485, 0x03:WG, 0x04:WIFI, 0x05:GPRS, 0x06:USB
        if (0x06 < (response.getBuffer()[7] & 0xFF)) {
            return (response.getBuffer()[7] & 0xFF) << 8;
        }

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    public int getRs485Address(byte reader_id, final byte[] rs485_address) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdGetRs485Address);
        processor.setRequestValidator(() -> rs485_address != null && rs485_address.length == 1);
        processor.setResponseUtilizer(response -> rs485_address[0] = response.getBuffer()[7]);

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdGetRs485Address);
    }

    private int getRs485AddressResponseValidator(@NonNull CacheNode response) {
        final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
        if (1 != body_len || 0xFF == (response.getBuffer()[7] & 0xFF)) {
            return EraLinkUHFReaderApiError.ErrBadResponse;
        }

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    public int getTriggerModeDelayStopReadTime(byte reader_id, final byte[] time) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdGetTriggerModeDelayStopReadTime);
        processor.setRequestValidator(() -> time != null && time.length == 1);
        processor.setResponseUtilizer(response -> time[0] = response.getBuffer()[7]);

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdGetTriggerModeDelayStopReadTime);
    }

    private int getTriggerModeDelayStopReadTimeResponseValidator(@NonNull CacheNode response) {
        final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
        if (1 != body_len) {
            return EraLinkUHFReaderApiError.ErrBadResponse;
        }

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    public int getAutoReadParams(byte reader_id, final byte[] bank, final short[] offset, final byte[] length,
                                 final byte[] access_password) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdGetAutoReadParams);
        processor.setRequestValidator(() ->
                bank != null && bank.length == 1 && offset != null && offset.length == 1 &&
                        length != null && length.length == 1 && access_password != null &&
                        access_password.length == 4
        );
        processor.setResponseUtilizer(response -> {
            bank[0] = response.getBuffer()[7];
            offset[0] = Utils.u16FromLittleEndianU8s(response.getBuffer(), 8);
            length[0] = response.getBuffer()[10];
            System.arraycopy(response.getBuffer(), 11, access_password, 0, 4);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdGetAutoReadParams);
    }

    private int getAutoReadParamsResponseValidator(@NonNull CacheNode response) {
        final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
        if (1 == body_len) {
            return (response.getBuffer()[7] & 0xFF) << 8;
        }

        // 读取区域:0x00 EPC,0x01:TID, 0x02:EPC+TID, 0x03 :USER, 0x04:EPC+USER
        if (8 != body_len || 0x04 < (response.getBuffer()[7] & 0xFF)) {
            return EraLinkUHFReaderApiError.ErrBadResponse;
        }

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    public int getRssiFilter(byte reader_id, final byte[] enable, final short[] rssi_value) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdGetRssiFilter);
        processor.setRequestValidator(() ->
                enable != null && enable.length == 1 && rssi_value != null && rssi_value.length == 1
        );
        processor.setResponseUtilizer(response -> {
            enable[0] = response.getBuffer()[7];
            rssi_value[0] = (short) (0xFFFF - (Utils.u16FromLittleEndianU8s(response.getBuffer(), 8) & 0xFFFF));
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdGetRssiFilter);
    }

    private int getRssiFilterResponseValidator(@NonNull CacheNode response) {
        final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
        if (1 == body_len) {
            return (response.getBuffer()[7] & 0xFF) << 8;
        }

        // 0:Disable RSSI Filter  1:Enable RSSI Filter
        if (3 != body_len || 1 < (response.getBuffer()[7] & 0xFF)) {
            return EraLinkUHFReaderApiError.ErrBadResponse;
        }

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    public int getTagFilter(byte reader_id, final byte[] enable, final byte[] filter_time) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdGetTagFilter);
        processor.setRequestValidator(() ->
                enable != null && enable.length == 1 && filter_time != null && filter_time.length == 1
        );
        processor.setResponseUtilizer(response -> {
            enable[0] = response.getBuffer()[7];
            filter_time[0] = response.getBuffer()[8];
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdGetTagFilter);
    }

    private int getTagFilterResponseValidator(@NonNull CacheNode response) {
        final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
        if (1 == body_len) {
            return (response.getBuffer()[7] & 0xFF) << 8;
        }

        // 0:关闭过滤   1：开启过滤
        if (2 != body_len || 1 < (response.getBuffer()[7] & 0xFF)) {
            return EraLinkUHFReaderApiError.ErrBadResponse;
        }

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    public int getWifiParams(byte reader_id, byte index, final String[] params) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdGetWifiParams);
        processor.setRequestValidator(() -> {
            // // 0: ip,  1： ap, 2： sock,  3： mac
            return (index & 0xFF) <= 3 && params != null && 1 == params.length;
        });
        processor.setRequestor(reader_id_ -> writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdGetWifiParams, new byte[]{index}, 0, 1));
        processor.setResponseValidator(response -> {
            final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
            if (1 == body_len) {
                return (response.getBuffer()[7] & 0xFF) << 8;
            }

            boolean bad_data = true;
            do {
                if (3 > body_len) {
                    break;
                }

                // 0: ip,  1： ap, 2： sock,  3： mac
                if (index != response.getBuffer()[7]) {
                    break;
                }

                if (3 == index) { // mac
                    if (7 > body_len) {
                        break;
                    }
                    params[0] = Utils.bytesToHexString(response.getBuffer(), 8, body_len - 1);
                } else {
                    params[0] = new String(response.getBuffer(), 8, body_len - 1);
                    final String[] fields = params[0].split(",");
                    if (0 == index) { // ip: ipmode, ip, mask, gateway, dns
                        if (37 > body_len || 5 != fields.length) {
                            break;
                        }
                    } else if (1 == index) { // ap: ssid, key
                        if (4 > body_len || 2 != fields.length) {
                            break;
                        }
                    } else if (2 == index) { // socket: mode, ip, port
                        if (15 > body_len || 3 != fields.length) {
                            break;
                        }
                    }
                }

                bad_data = false;
                break;
            } while (false);

            if (bad_data) {
                params[0] = null;
                return EraLinkUHFReaderApiError.ErrBadResponse;
            }

            return EraLinkUHFReaderApiError.ErrNoError;
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdGetWifiParams);
    }

    public int getGprsParams(byte reader_id, byte index, final String[] params) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdGetGprsParams);
        processor.setRequestValidator(() -> {
            // 0: socket,  1： apn
            return (index & 0xFF) <= 1 && params != null && 1 == params.length;
        });
        processor.setRequestor(reader_id_ -> writeRequest(reader_id_, EraLinkUHFReaderCmdCode.CmdGetGprsParams, new byte[]{index}, 0, 1));
        processor.setResponseValidator(response -> {
            final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
            if (1 == body_len) {
                return (response.getBuffer()[7] & 0xFF) << 8;
            }

            boolean bad_data = true;
            do {
                if (2 > body_len) {
                    break;
                }

                // 0: socket,  1： apn
                if (index != response.getBuffer()[7]) {
                    break;
                }

                params[0] = new String(response.getBuffer(), 8, body_len - 1);
                final String[] fields = params[0].split(",");
                if (0 == index) { // socket: protocol, ip/domain, port
                    if (14 > body_len || 3 != fields.length) {
                        break;
                    }
                } else { // apn: apn, account, password, auth
                    if (4 > body_len || 4 != fields.length) {
                        break;
                    }
                    Integer auth = Utils.tryParseInt(fields[3]);
                    if (auth == null || 0 > auth || 3 < auth) {
                        break;
                    }
                }

                bad_data = false;
                break;
            } while (false);

            if (bad_data) {
                params[0] = null;
                return EraLinkUHFReaderApiError.ErrBadResponse;
            }

            return EraLinkUHFReaderApiError.ErrNoError;
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdGetGprsParams);
    }

    public int getBuzzerEnable(byte reader_id, final byte[] enable) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdGetBuzzerEnable);
        processor.setRequestValidator(() -> enable != null && enable.length == 1);
        processor.setResponseUtilizer(response -> enable[0] = response.getBuffer()[7]);

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdGetBuzzerEnable);
    }

    private int getBuzzerEnableValidator(@NonNull CacheNode response) {
        final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
        if (1 != body_len) {
            return EraLinkUHFReaderApiError.ErrBadResponse;
        }

        // 0：关闭   1：开启
        if (1 < (response.getBuffer()[7] & 0xFF)) {
            return (response.getBuffer()[7] & 0xFF) << 8;
        }

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    public int getBaud(byte reader_id, final int[] baud) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdGetBaud);
        processor.setRequestValidator(() -> baud != null && baud.length == 1);
        processor.setResponseUtilizer(response -> baud[0] = Utils.u32FromLittleEndianU8s(response.getBuffer(), 7));

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdGetBaud);
    }

    private int getBaudResponseValidator(@NonNull CacheNode response) {
        final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
        if (body_len == 1) {
            return (response.getBuffer()[7] & 0xFF) << 8;
        }

        if (4 != body_len) {
            return EraLinkUHFReaderApiError.ErrBadResponse;
        }

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    public int getWgParams(byte reader_id, final byte[] protocol_type, final byte[] data_start_address,
                           final byte[] pulse_width, final byte[] pulse_cycle, final byte[] data_interval) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdGetWgParams);
        processor.setRequestValidator(() ->
                protocol_type != null && protocol_type.length == 1 &&
                data_start_address != null && data_start_address.length == 1 &&
                pulse_width != null && pulse_width.length == 1 &&
                pulse_cycle != null && pulse_cycle.length == 1 &&
                data_interval != null && data_interval.length == 1
        );
        processor.setResponseUtilizer(response -> {
            protocol_type[0] = response.getBuffer()[7];
            data_start_address[0] = response.getBuffer()[8];
            pulse_width[0] = response.getBuffer()[9];
            pulse_cycle[0] = response.getBuffer()[10];
            data_interval[0] = response.getBuffer()[11];
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdGetWgParams);
    }

    private int getWgParamsResponseValidator(@NonNull CacheNode response) {
        final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
        if (1 == body_len) {
            return (response.getBuffer()[7] & 0xFF) << 8;
        }

        if (5 != body_len) {
            return EraLinkUHFReaderApiError.ErrBadResponse;
        }

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    public int getRtcTime(byte reader_id, final byte[] rtc_time) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdGetRtcTime);
        processor.setRequestValidator(() ->
                rtc_time != null && rtc_time.length == 7
        );
        processor.setResponseUtilizer(response -> {
            final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
            System.arraycopy(response.getBuffer(), 7, rtc_time, 0, body_len);
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdGetRtcTime);
    }

    private int getRtcTimeResponseValidator(@NonNull CacheNode response) {
        final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
        if (1 == body_len) {
            return (response.getBuffer()[7] & 0xFF) << 8;
        }

        if (7 != body_len) {
            return EraLinkUHFReaderApiError.ErrBadResponse;
        }

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    public int getAllSystemParams(byte reader_id, final byte[] system_params, final int[] system_params_len) {
        Processor processor = processors.get(EraLinkUHFReaderCmdCode.CmdGetAllSystemParams);
        processor.setRequestValidator(() ->
                system_params != null && system_params_len != null && 1 == system_params_len.length &&
                        0 < system_params_len[0] &&  system_params_len[0] <= system_params.length
        );
        processor.setResponseValidator(response -> {
            final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
            if (body_len == 1) {
                return (response.getBuffer()[7] & 0xFF) << 8;
            }

            if (system_params_len[0] < body_len) {
                return EraLinkUHFReaderApiError.ErrInsufficientInputBuffer;
            }

            return EraLinkUHFReaderApiError.ErrNoError;
        });
        processor.setResponseUtilizer(response -> {
            final int body_len = Utils.u16FromLittleEndianU8s(response.getBuffer(), 5);
            System.arraycopy(response.getBuffer(), 7, system_params, 0, body_len);
            system_params_len[0] = body_len;
        });

        return doRequest(reader_id, EraLinkUHFReaderCmdCode.CmdGetAllSystemParams);
    }

    private int doRequest(byte reader_id, int cmdCode) {
        Processor processor = processors.get(cmdCode);
        if (null == processor) {
            return EraLinkUHFReaderApiError.ErrRequestFailed;
        }

        if (!processor.getRequestValidator().validate()) {
            return EraLinkUHFReaderApiError.ErrBadArg;
        }

        if (processor.isNeedNotifyRequestor()) {
            if (!waitCmd.compareAndSet(0, cmdCode)) {
                return EraLinkUHFReaderApiError.ErrNeedRetry;
            }
        }

        int ret;

        CacheNode response = null;
        do {
            ret = processor.getRequestor().request(reader_id);
            if (EraLinkUHFReaderApiError.ErrNoError != ret) {
                break;
            }

            if (processor.isNeedNotifyRequestor()) {
                try {
                    if (!semaphore.tryAcquire(3, TimeUnit.SECONDS)) {
                        ret = EraLinkUHFReaderApiError.ErrTimeout;
                        break;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    ret = EraLinkUHFReaderApiError.ErrNeedRetry;
                    break;
                }

                response = responseCache.remove(cmdCode);
                if (response == null) {
                    ret = EraLinkUHFReaderApiError.ErrWaitNoResponse;
                    break;
                }

                ret = processor.getResponseValidator().validate(response);
                if (EraLinkUHFReaderApiError.ErrNoError != ret) {
                    break;
                }

                processor.getResponseUtilizer().utilize(response);
            }
        } while (false);

        if (processor.isNeedNotifyRequestor()) {
            waitCmd.set(0);
        }

        CachePool.getInstance().release(response);

        return ret;
    }

    // 发送数据
    private int writeRequest(byte reader_id, int cmd_code, final byte[] data, final int offset, final int length) {
        final byte[] request = buildRequest(cmd_code, data, offset, length);
        Log.d(TAG, String.format("request: %02d%s", reader_id & 0xFF, Utils.bytesToHexString(request, 0, request.length)));

        if (role == 0) {
            return reader.write(request, 0, request.length);
        } else {
            synchronized (syncObj) {
                Client client = clients.get(reader_id);
                if (client == null) {
                    return EraLinkUHFReaderApiError.ErrCommunicationError;
                } else
                    return client.write(request, 0, request.length);
            }
        }
    }

    private byte[] buildRequest(int cmd_code, final byte[] body, int offset, int length) {
        final byte[] request = new byte[7 + length];
        request[0] = 0x43; request[1] = 0x4D; request[2] = (byte) cmd_code; request[3] = address;
        Utils.u16ToLittleEndianU8s((short)length, request, 4);
        System.arraycopy(body, offset, request, 6, length);
        request[6 + length] = Utils.bcc(request, 6, length);
        return request;
    }

    class ReadThread extends Thread {
        @Override
        public void run() {
            final int BufferMaxSize = 1024;
            byte[] buffer = new byte[BufferMaxSize];
            final int[] readSize = new int[] { 0 };
            while (threadRunning) {
                if (0 > readSize[0]) {
                    readSize[0] = 0;
                }
                final int ret = reader.read(buffer, readSize, BufferMaxSize - readSize[0]);
                if (EraLinkUHFReaderApiError.ErrNoError == ret) {
                    if (0 < readSize[0]) {
                        Log.v(TAG, String.format("reader read: %s", Utils.bytesToHexString(buffer, 0, readSize[0])));
                        parseReport(buffer, readSize);
                    }
                } else {
                    Log.e(TAG, String.format("reader read failed, reason: %s", ret));
                    reportDisconnect((byte) 0);
                    break;
                }
            }

            Log.e(TAG, "receive thread exit !!!");
        }

        private void parseReport(byte[] buffer, final int[] readSize) {
            // Header[2] | Command | Address | Len_High | Len_Low | Data[0…N]  | BCC
            //  2 bytes  | 1 byte  |  1 bytes | 1 byte  |  1 byte  | 0~N bytes  | 1 byte
            // 0x43 0x4D |   CMD   |    0x00  |  N * 256  |  n  |   ---      |
            if (7 > readSize[0]) {
                return;
            }

            int index = 0;
            while (index < readSize[0] - 1) {
                if (0x43 != buffer[index] || 0x4D != buffer[index + 1]) {
                    ++index;
                    continue;
                }

                if (readSize[0] < (index + 7)) {
                    break;
                }

                final int body_len = Utils.u16FromLittleEndianU8s(buffer, index + 4);
                if (513 < body_len) {// data区最大长度512
                    ++index;
                    continue;
                }
                if (readSize[0] < (index + body_len + 7)) { // 数据不完整，需要继续接收
                    break;
                }

                if (buffer[index + 6 + body_len] != Utils.bcc(buffer, index + 6, body_len)) {
                    Log.e(TAG, String.format("bad data: %s", Utils.bytesToHexString(buffer, 0, readSize[0])));
                    // BCC校验不合法
                    ++index;
                    continue;
                }

                CacheNode cacheNode = CachePool.getInstance().obtain(8 + body_len);
                cacheNode.getBuffer()[0] = 0;
                System.arraycopy(buffer, index, cacheNode.getBuffer(), 1, 7 + body_len);
                cacheNode.setBufferSize(8 + body_len);
                reportQueue.add(cacheNode);

                index += (body_len + 7);
            }

            if (0 != index) {
                readSize[0] -= index;
                if (readSize[0] < 0) {
                    readSize[0] = 0;
                } else {
                    System.arraycopy(buffer, index, buffer, 0, readSize[0]);
                }
            }
        }
    }

    class MIOThread extends Thread {
        private byte reader_id = 0;
        @Override
        public void run() {
            super.run();
            while (threadRunning) {
                synchronized (syncObj) {
                    for (Iterator<Map.Entry<Byte, Client>> it = clients.entrySet().iterator(); it.hasNext(); ) {
                        Map.Entry<Byte, Client> item = it.next();
                        if (item.getValue().isMarkedAsRemoved()) {
                            reportDisconnect(item.getKey());

                            item.getValue().close();
                            it.remove();
                        }
                    }
                }

                int nKeys;
                try {
                    nKeys = selector.select(20);
                } catch (IOException e) {
                    e.printStackTrace();
                    break;
                }

                if (nKeys == 0) {
                    continue;
                }

                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    if (key.isAcceptable()) {
                        if (!handleAccept(key)) {
                            break;
                        }
                    } else if (key.isReadable()) {
                        handleRead(key);
                    }
                    iterator.remove();
                }
            }
        }

        private byte getNextReaderId() {
            reader_id++;
            if (reader_id == 0) {
                reader_id = 1;
            }
            return reader_id;
        }

        private boolean handleAccept(SelectionKey key) {
            ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
            SocketChannel socketChannel = null;
            try {
                socketChannel = serverSocketChannel.accept();
                SocketAddress socketAddress = socketChannel.getRemoteAddress();
                socketChannel.configureBlocking(false);
                Client client = new Client(getNextReaderId(), socketChannel);
                if (socketAddress instanceof InetSocketAddress) {
                    InetSocketAddress address = (InetSocketAddress) socketAddress;
                    client.setHostString(address.getHostString());
                } else {
                    client.setHostString(socketAddress.toString());
                }
                socketChannel.register(selector, SelectionKey.OP_READ, client);
                synchronized (syncObj) {
                    clients.put(client.getReaderId(), client);
                }
            } catch (IOException e) {
                e.printStackTrace();
                if (socketChannel != null) {
                    try {
                        socketChannel.close();
                    } catch (IOException ioException) {
                        ioException.printStackTrace();
                    }
                }
                return false;
            }

            return true;
        }

        private void handleRead(SelectionKey key) {
            Client client = (Client) key.attachment();
            if (EraLinkUHFReaderApiError.ErrNoError != client.read(reportQueue)) {
                client.setMarkedAsRemoved(true);
            }
        }
    }

    private void reportDisconnect(byte reader_id) {
        final byte[] report = { reader_id, 0x43, 0x4D, (byte)0xFF, 0x00, 0x00, 0x00, 0x00 };
        CacheNode cacheNode = CachePool.getInstance().obtain(report.length);
        System.arraycopy(report, 0, cacheNode.getBuffer(), 0, report.length);
        cacheNode.setBufferSize(report.length);
        reportQueue.add(cacheNode);
    }

    class DispatchThread extends Thread {
        @Override
        public void run() {
            while (threadRunning) {
                CacheNode cacheNode;
                try {
                    cacheNode = reportQueue.poll(20, TimeUnit.MILLISECONDS);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    Log.w(TAG, "ParseThread interrupted.");
                    break;
                }

                if (cacheNode == null) {
                    continue;
                }

                Log.d(TAG, String.format("report %s", Utils.bytesToHexString(cacheNode.getBuffer(), 0, cacheNode.getBufferSize())));

                final int cmd = cacheNode.getBuffer()[3] & 0xFF;

                Processor processor = processors.get(cmd);
                if (processor == null) {
                    Log.w(TAG, String.format("unknown cmd: %d", cmd));
                    CachePool.getInstance().release(cacheNode);
                    continue;
                }

                if (processor.isNeedNotifyRequestor()) {
                    if (waitCmd.compareAndSet(cmd, cmd)) {
                        responseCache.put(cmd, cacheNode);
                        semaphore.release();
                    } else {
                        CachePool.getInstance().release(cacheNode);
                    }
                } else {
                    if (EraLinkUHFReaderApiError.ErrNoError != processor.getResponseValidator().validate(cacheNode)) {
                        CachePool.getInstance().release(cacheNode);
                        continue;
                    }

                    processor.getReportUtilizer().utilize(cacheNode);
                    CachePool.getInstance().release(cacheNode);
                }
            }
        }
    }
}
